#pragma once 
//----------------------------------------------------------------------------------------------------------------
//			Filename:		ObjLoader.h
//			  Author:		Adrian De Barro
//			    Date:		12/08/2015
//----------------------------------------------------------------------------------------------------------------
//			  Status: REFACTORING
//----------------------------------------------------------------------------------------------------------------
#include <random>
#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
#include <fstream>
#include <boost\tokenizer.hpp>
#include <time.h>
#include <stdio.h>
#include <map>

#include "..\CoreClasses\RGBObject\RGBColor.h"
#include "..\Material\Material.h"
#include "..\Material\Matte.h"
#include "..\CoreClasses\Logger\Logger.h"
#include "..\CoreClasses\GeometricObjects\Quad.h"
#include "..\Material\Material.h"
#include "..\CoreClasses\GeometricObjects\Triangle.h"

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
using Engine::Triangle;
using Engine::TriangleVertex;
using std::vector;
using Engine::Matte;

typedef boost::tokenizer<boost::char_separator<char>> tokeniz;

namespace Engine
{
	//----------------------------------------------------------------------------------------------------------------
	//			Class Name: ObjLoader
	//		   Description: 
	//----------------------------------------------------------------------------------------------------------------
	//			    Status: REFACTORING
	//----------------------------------------------------------------------------------------------------------------
	class ObjLoader
	{
	public:
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: LoadFile
		//		Return Value: vector<GeometricObjects*>
		//		  Parameters: string
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		vector<GeometricObjects*> LoadFile(string path)
		{
			return ParseObjFile(path);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: LoadFile
		//		Return Value: vector<GeometricObjects*>
		//		  Parameters: string path, Point3D centroid, bool Obj uses textures
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		vector<GeometricObjects*> LoadFile(string path, Point3D centroid, bool p_useTextures = false)
		{
			return ParseObjFile(path, centroid, p_useTextures);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: ParseStringSentence
		//		Return Value: VOID
		//		  Parameters: string to parse, string splittingString, vector<string>& output list
		//			  Status: 
		//					  TODO: 
		//----------------------------------------------------------------------------------------------------------------
		inline void ParseStringSentence(std::string toParse, std::string splittingString, std::vector<std::string>& output)
		{
			boost::char_separator<char> sep(splittingString.c_str());
			tokeniz tok(toParse, sep);
			int index = 0;
			for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
			{
				output.push_back(tokIter.current_token().c_str());
			}
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: ParseString
		//		Return Value: vector<string>
		//		  Parameters: string toParse, string splittingString
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		vector<std::string> ParseString(std::string toParse, std::string splittingString)
		{
			boost::char_separator<char> sep(splittingString.c_str());
			tokeniz tok(toParse, sep);
			vector<string> output;

			int index = 0;
			for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
			{
				output.push_back(tokIter.current_token().c_str());
			}

			return output;
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: ParseValues
		//		Return Value: VOID
		//		  Parameters: string toParse, string splitting string, vector<string>& output
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void ParseValues(std::string toParse, std::string splittingString, vector<string>& output)
		{
			boost::char_separator<char> sep(splittingString.c_str());
			tokeniz tok(toParse, sep);
			int amount = 0;
			for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
			{
				amount++;
			}

			int index = 0;
			for (tokeniz::iterator tokIter = tok.begin(); tokIter != tok.end(); ++tokIter)
			{
				if (index == 0 | index > 1)
				{
					output.push_back(tokIter.current_token().c_str());
					if (amount == 1)
					{
						output.push_back("");
						output.push_back("");
					}
				}
				else
				{
					int size = output[0].size();
					char atSize = toParse.at(size);
					char atSize1 = toParse.at(size + 1);

					std::string check = std::string() + atSize + atSize1;

					if (std::strcmp(check.c_str(), "//") == 0)
					{
						output.push_back("");
						output.push_back(tokIter.current_token().c_str());
					}
					else if (amount == 2)
					{
						output.push_back(tokIter.current_token().c_str());
						output.push_back("");
					}
					else
					{
						output.push_back(tokIter.current_token().c_str());
					}

				}

				index++;
			}
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetMaterials
		//		Return Value: VOID
		//		  Parameters: path, map<string, Materials*> materials, bool contains texture
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void GetMaterials(string path, map<string, Engine::Material*>& allMaterials, bool containsTextures = false)
		{
			bool newfile = true;
			int index = 0;
			std::string detailsArray[2];
			bool firstTime = false;
			bool finishedFlag = false;
			//string fullPath = "C:\\Users\\Adrian\\Desktop\\" + path;
			//string fullPath = "\ObjFiles\\"+path;
			std::ifstream newFile(path);
			string line;
			if (newFile.is_open())
			{
				try
				{
					vector<string> tokens;
					string materialName = "";
					RGBColor diffuseColor(0);
					//RGBColor specularColor();
					while (std::getline(newFile, line))
					{
						//char firstChar = line.at(0);
						vector<string> tokens;
						if (line.compare("") == 0)
						{
							continue;
						}
						else
						{
							ParseStringSentence(line, " ", tokens);
						}

						if (line.at(0) == '#')
						{
							continue;
						}
						else if (tokens.size() > 0 && strcmp(tokens[0].c_str(), "newmtl") == 0)
						{
							if (!firstTime)
							{
								materialName = tokens[1];
								firstTime = true;
							}
							else
							{
								//first save then clear and store the new texture name

								bool solidColorExists = detailsArray[0].compare("") != 0;
								bool textureExists = detailsArray[1].compare("") != 0;

								Engine::Matte* currentMaterial;

								//only color
								if (solidColorExists && !textureExists)
								{
									vector<std::string> subTokens;

									ParseStringSentence(detailsArray[0], " ", subTokens);

									diffuseColor = RGBColor((float)atof(subTokens[0].c_str()), (float)atof(subTokens[1].c_str()), (float)atof(subTokens[2].c_str()));

									Engine::Matte* currentMaterial = new Engine::Matte(materialName, 0.75, diffuseColor);
									allMaterials[materialName] = currentMaterial;

								}
								else if (!solidColorExists && textureExists)
								{

									Engine::Matte* currentMaterial = new Engine::Matte(materialName, 0.75, detailsArray[1]);
									allMaterials[materialName] = currentMaterial;
								}
								else
								{
									vector<std::string> subTokens;

									ParseStringSentence(detailsArray[0], " ", subTokens);

									diffuseColor = RGBColor((float)atof(subTokens[0].c_str()), (float)atof(subTokens[1].c_str()), (float)atof(subTokens[2].c_str()));

									Engine::Matte* currentMaterial = new Engine::Matte(materialName, 0.75, detailsArray[1], diffuseColor);
									allMaterials[materialName] = currentMaterial;
								}

								detailsArray[0] = "";
								detailsArray[1] = "";

								materialName = tokens[1];
							}
						}
						else  if (tokens.size() > 0 && strcmp(tokens[0].c_str(), "Kd") == 0)
						{
							detailsArray[0] = tokens[1] + " " + tokens[2] + " " + tokens[3];
						}
						else if (tokens.size() > 0 && strcmp(tokens[0].c_str(), "map_Kd") == 0)
						{
							/*
								format is : map_Kd filename.ppm
								*/
							//name of file token [1]
							stringstream currentPath;

							vector<string> pathTokens;
							ParseStringSentence(path, "\\", pathTokens);

							for (int index = 0; index < pathTokens.size() - 1; index++)
							{
								if (index == 0)
								{
									currentPath << ".";
								}
								else
								{
									currentPath << "\\";
									currentPath << pathTokens[index];
								}

							}

							currentPath << "\\" << tokens[1];

							//fix here tonight
							detailsArray[1] = currentPath.str();
						}
					}
				}
				catch (exception e)
				{
					cout << "problem at line " << line << endl;
				}
				newFile.close();
			}
			else
			{
				cout << "mtl file not found" << endl;
			}
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: ParseObjFile
		//		Return Value: VOID
		//		  Parameters: string path, point3D objectcenter, bool usesTextures
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		vector<GeometricObjects*> ParseObjFile(string path, Point3D objectCenter = Point3D(0), bool usesTextures = false)
		{
			vector<Point3D> allVertices;
			vector<GeometricObjects*> allTriangles;
			vector<Point3D> allNormals;
			vector<Point3D> allTextureVertex;
			std::map<string, Engine::Material*> allMaterials;
			//updated 
			std::map<string, TriangleVertex> producedVertices;
			string emptyString = "";

			bool materialsAvailable = false;

			bool v = false;
			bool vt = false;
			bool vn = false;

			string mtlObjectName = "";
			char secondChar = '_';

			string line;
			std::ifstream myfile(path);
			if (myfile.is_open())
			{
				try
				{
					while (std::getline(myfile, line))
					{
						vector<string> tokens;
						string currentToken = "";
						ParseStringSentence(line, " ", tokens);

						if (line.compare(emptyString) == 0 || tokens.size() == 0)
						{
							continue;
						}

						char firstChar = line.at(0);
						if (line.length() > 1)
							secondChar = line.at(1);

						if (firstChar == '#')
						{
							continue;
						}

						//if materials mtllib 
						else if (firstChar == 'm' && secondChar == 't')
						{
							//load materials
							string materialPath = tokens[1];

							vector<string> pathTokens;
							ParseStringSentence(path, "\\", pathTokens);

							stringstream textFilePath;
							if (pathTokens.size() > 1)
							{
								for (int index = 0; index < pathTokens.size() - 1; index++)
								{
									if (index == 0)
										textFilePath << ".\\" << pathTokens[index];
									else
										textFilePath << "\\" << pathTokens[index];
								}
							}


							textFilePath << "\\" << materialPath;
							GetMaterials(textFilePath.str(), allMaterials, usesTextures);
							materialsAvailable = true;
						}
						//when it finds usemtl 
						else if (firstChar == 'u' && secondChar == 's')
						{
							mtlObjectName = tokens[1];
						}
						else if (tokens.size() > 0)
						{
							//first token is always what is the type mof the object eg V or F
							string typeOfLine = tokens[0];
							if (firstChar == 'f')
							{
								//tokens = vector<string>(tokens.begin() + 1, tokens.end());

								try
								{
									//triangle
									if (tokens.size() == 4)
									{
										vector<string> output;

										TriangleVertex a;
										TriangleVertex b;
										TriangleVertex c;

										for (int subIndex = 1; subIndex < 4; subIndex++)
										{
											TriangleVertex& pointer = subIndex == 1 ? a : subIndex == 2 ? b : c;
											vector<std::string> output;

											ParseValues(tokens[subIndex], "/", output);

											std::map<string, TriangleVertex>::iterator it = producedVertices.find(tokens[subIndex]);

											if (it != producedVertices.end())
											{
												pointer = producedVertices[tokens[subIndex]];
											}
											else
											{
												Point3D tempVertex = output[0].compare("") == 0 ? NULL : allVertices.at((int)atoi(output[0].c_str()) - 1);
												Point3D temptextureVertex = output[1].compare("") == 0 ? NULL : allTextureVertex.at(atoi(output[1].c_str()) - 1);
												Point3D tempNormal = output[2].compare("") == 0 ? NULL : allNormals.at(atoi(output[2].c_str()) - 1);
												producedVertices[tokens[subIndex]] = TriangleVertex(tempVertex, tempNormal, temptextureVertex);;
												pointer = producedVertices[tokens[subIndex]];
											}
										}

										//cout << mtlObjectName << endl;
										Engine::Material *currentMaterial = allMaterials[mtlObjectName];

										if (currentMaterial != NULL)
										{
											Triangle *currentTriangle = new Triangle(a, b, c, currentMaterial, mtlObjectName);
											allTriangles.push_back(currentTriangle);
										}
										else
										{
											cout << "we may have a problem" << endl;
										}
									}
									else if (tokens.size() == 5)
									{
										vector<string> output;

										TriangleVertex vertexArray[4];

										for (int subIndex = 1; subIndex <= 4; subIndex++)
										{
											TriangleVertex& pointer = vertexArray[subIndex - 1];
											vector<std::string> output;
											ParseValues(tokens[subIndex], "/", output);
											std::map<string, TriangleVertex>::iterator it = producedVertices.find(tokens[subIndex]);

											if (it != producedVertices.end())
											{
												pointer = producedVertices[tokens[subIndex]];
											}
											else
											{
												Point3D tempVertex = output[0].compare("") == 0 ? NULL : allVertices.at((int)atoi(output[0].c_str()) - 1);
												Point3D temptextureVertex = output[1].compare("") == 0 ? NULL : allTextureVertex.at(atoi(output[1].c_str()) - 1);
												Point3D tempNormal = output[2].compare("") == 0 ? NULL : allNormals.at(atoi(output[2].c_str()) - 1);
												producedVertices[tokens[subIndex]] = TriangleVertex(tempVertex, tempNormal, temptextureVertex);;
												pointer = producedVertices[tokens[subIndex]];
											}
										}

										Engine::Material *currentMaterial = allMaterials[mtlObjectName];
										if (materialsAvailable)
										{
											Triangle *firstTriangle = new Triangle(vertexArray[0], vertexArray[1], vertexArray[2], currentMaterial, mtlObjectName);
											Triangle *secondTriangle = new Triangle(vertexArray[2], vertexArray[3], vertexArray[0], currentMaterial, mtlObjectName);
											allTriangles.push_back(firstTriangle);
											allTriangles.push_back(secondTriangle);
										}
										else
										{
											Triangle *firstTriangle = new Triangle(vertexArray[0], vertexArray[1], vertexArray[2]);
											Triangle *secondTriangle = new Triangle(vertexArray[2], vertexArray[3], vertexArray[0]);
											allTriangles.push_back(firstTriangle);
											allTriangles.push_back(secondTriangle);
										}
											

										
									}

								}
								catch (std::exception e)
								{
									std::cout << "error " << line << std::endl;
								}
							}
							else if (firstChar == 'v' && secondChar == 'n')
							{
								float xCoor = atof(tokens[1].c_str());
								float yCoor = atof(tokens[2].c_str());
								float zCoor = atof(tokens[3].c_str());

								Point3D currentVector(xCoor, yCoor, zCoor);
								allNormals.push_back(currentVector);
							}
							else if (firstChar == 'v' && secondChar == 't')
							{
								float xCoor = atof(tokens[1].c_str());
								float yCoor = atof(tokens[2].c_str());
								//float zCoor = atof(tokens[3].c_str());

								Point3D currentVector(xCoor, yCoor, 0);
								allTextureVertex.push_back(currentVector);
							}
							else if (firstChar == 'v')
							{
								float xCoor = atof(tokens[1].c_str());
								float yCoor = atof(tokens[2].c_str());
								float zCoor = atof(tokens[3].c_str());

								Point3D currentVector((xCoor + objectCenter.x), (yCoor + objectCenter.y), (zCoor + objectCenter.z));
								allVertices.push_back(currentVector);
							}

						}
						tokens.clear();
					}

					myfile.close();
				}
				catch (exception e)
				{
					cout << "Error when reading line" << endl;
				}
			}
			else
			{
				cout << "Error when opening the file" << endl;
			}

			Engine::Logger::LogTrace("normals --- " + to_string(allNormals.size()));
			Engine::Logger::LogTrace("all vertices --- " + to_string(allVertices.size()));
			Engine::Logger::LogTrace("all materials --- " + to_string(allMaterials.size()));
			Engine::Logger::LogTrace("all triangles ---" + to_string(allTriangles.size()));


			return allTriangles;
		}

	private:
	};
	//----------------------------------------------------------------------------------------------------------------
}
//----------------------------------------------------------------------------------------------------------------

